LambdaのINITフェーズではメモリ128MでもCPUパワーをフルに使える?!boost host CPUの動きを確認してみた
CX事業本部@大阪の岩田です。
Lambdaのランタイムではメモリ割り当てに応じて利用可能なCPUパワーが変わることが知られており、メモリ割当1769Mから1vCPUのフルパワーが利用可能になります。私も以前は勘違いしていたのですが、このメモリ割り当てに比例してCPUパワーが変わるという挙動はINITフェーズにおいては適用されません。
このブログではLambdaのINITフェーズにおけるCPUパワーについて紹介&検証していきます。
boost host CPU
皆さんはboost host CPUという概念をご存知でしょうか?LambdaのINITフェーズにおいて、最初の10秒間に関してはメモリ割り当てに応じたvCPUではなく、ホストCPU容量のバーストが割り当てられるという仕様があり、この仕様を指してboost host CPUと呼ぶようです。
boost host CPUの概念については以下のような資料で解説されています
re: Postより
Lambda 関数を初期化すると、Lambda はホスト CPU 容量のバーストを最大 10 秒間割り当てます。
Java Lambda 関数のパフォーマンスを向上させる | AWS re:Post
AWS Lambda Performance Tuning Deep Diveより
re:Invent 2019のセッション「Best practices for AWS Lambda and Java」
これらの資料で解説されているboost host CPUの振る舞いが期待通りかを検証していきたいと思います。
環境
今回検証に利用した環境です
- メモリ割当: 検証パターンに応じて128Mと1769Mを切り替え
- CPUアーキテクチャ: arm64
- タイムアウト:120秒
- ランタイム: Python3.12
- ランタイムバージョンARN: arn:aws:lambda:ap-northeast-1::runtime:5eaca0ecada617668d4d59f66bf32f963e95d17ca326aad52b85465d04c429f5
検証手順
色々なパターンでCPUバウンドな処理を実行し、実行にかかった所要時間を比較してみます。まずChtGPTにお願いしてCPUバウンドな処理を書いてもらいました。
def calculate_prime_numbers(limit): # 簡単な素数計算を模倣するCPUバウンドな処理 primes = [] for num in range(2, limit + 1): is_prime = all(num % i != 0 for i in range(2, int(num**0.5) + 1)) if is_prime: primes.append(num) return primes
この関数を利用して、以下のようなLambdaのコードを準備します。
import time import json def calculate_prime_numbers(limit): start = time.perf_counter() primes = [] for num in range(2, limit + 1): is_prime = all(num % i != 0 for i in range(2, int(num**0.5) + 1)) if is_prime: primes.append(num) end = time.perf_counter() processing_time = end -start print(json.dumps({'processing_time': processing_time})) return primes # 1.boost host CPUの恩恵を受けるパターン calculate_prime_numbers(1000000) # 2.INITフェーズだが、10秒経過したためboost host CPUの恩恵を受けられないパターン # time.sleep(10) # calculate_prime_numbers(1000000) def lambda_handler(event, context): # 3.INVOKEフェーズのためboost host CPUの恩恵を受けられないパターン # calculate_prime_numbers(1000000) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
コメントアウト箇所を切り替えながら、色々なパターンで所要時間を計測してみます。
boost host CPUの恩恵を受けるパターン
メモリ割り当て128MでINITフェーズ内でCPUバウンドな処理を実行するパターンです。以下の部分のコメントアウトを解除して実行します。
# 1.boost host CPUの恩恵を受けるパターン calculate_prime_numbers(1000000)
calculate_prime_numbers
の実行に要した時間は6.021214539秒でした。
INITフェーズだが、10秒経過したためboost host CPUの恩恵を受けられないパターン
メモリ割り当ては128のままで、以下のコメントアウトを解除して検証します。10秒のsleepを挟んでいるので、calculate_prime_numbers
を実行する頃にはboost host CPUの恩恵が受けられなくなり、メモリ128Mに対応した貧弱なCPUパワーしか利用できないはずです。以下のようにコメントアウトを解除します。
time.sleep(10) calculate_prime_numbers(1000000)
このパターンだとcalculate_prime_numbers
の実行に要した時間は84.34408051400001秒でした。先程のパターンと比較して14倍程度の時間を要しています。
INVOKEフェーズのためboost host CPUの恩恵を受けられないパターン(メモリ128M)
次はメモリ割当128のままINVOKEフェーズ内でcalculate_prime_numbers
を実行してみます。以下のようにコメントアウトを解除して実行します。
def lambda_handler(event, context): # 3.INVOKEフェーズのためboost host CPUの恩恵を受けられないパターン calculate_prime_numbers(1000000)
このパターンだとcalculate_prime_numbers
の実行に要した時間は85.31145541300003秒でした。INITフェーズかつboost host CPUの恩恵無しのパターンと同程度の所要時間となっていることが分かります。
INVOKEフェーズのためboost host CPUの恩恵を受けられないパターン(メモリ1769M)
最後にメモリ割当を1769Mに変更し、先程と同様INVOKEフェーズ内でcalculate_prime_numbers
を実行してみます。結果、calculate_prime_numbers
の実行に要した時間は6.078802296999925}秒で、boost host CPUの恩恵を受けたパターンと同程度の所要時間でした。
計測結果のまとめ
計測結果をまとめると以下のようになりました。各パターン1回ずつしか計測していませんが、メモリ割当が128Mの場合でもboost host CPUの恩恵によってCPUバウンドな処理が高速に実行できていることが分かります。
boost host CPU | 実行箇所 | メモリ割り当て | 所要時間 |
---|---|---|---|
有 | INITフェーズ | 128M | 約6.02秒 |
無 | INITフェーズ | 128M | 約84.34秒 |
無 | INVOKEフェーズ | 128M | 約85.31秒 |
無 | INVOKEフェーズ | 1769M | 約6.08秒 |
まとめ
boost host CPUについて検証してみました。Lambdaのパフォーマンスチューニングやコールドスタートの改善に取り組む場合はboost host CPUの仕様について理解しておくと良いでしょう。